package dnl.jexem.camouflaj;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;

import dnl.jexem.cmd.Command;

public abstract class DirBasedCommandManager extends RemoteCommandManager {

	static final Logger logger = LoggerFactory.getLogger(DirBasedCommandManager.class
			.getSimpleName());

	protected String workingDir;
	protected XStream xstream = new XStream(new DomDriver());

	public DirBasedCommandManager() {
		super();
	}

	public String getWorkingDir() {
		return workingDir;
	}

	public void setWorkingDir(String targetDir) {
		this.workingDir = targetDir;
	}

	@Override
	public String placeCommand(Command command) {
		logger.info("Placing new command: " + command.getCommand());
		CommandInfo commandInfo = new CommandInfo(command, new Date());
		String xml = xstream.toXML(commandInfo);
		try {
			saveFile(getCommandFileName(commandInfo.getCommandId()), xml);
		} catch (IOException e) {
			logger.error("", e);
		}
		return commandInfo.getCommandId();
	}

	@Override
	public CommandInfo[] retrievePendingCommands() {
		Set<String> commandFiles;
		Set<String> execFiles;
		try {
			commandFiles = getFilesFromWorkDir("command_");
			execFiles = getFilesFromWorkDir("exec_");
		} catch (IOException e) {
			logger.error("", e);
			return null;
		}
		Set<String> executedCommandIds = new HashSet<String>();
		List<CommandInfo> pending = new ArrayList<CommandInfo>();
		for (String execFile : execFiles) {
			executedCommandIds.add(extractCommandID(execFile));
		}
		for (String commandFile : commandFiles) {
			String commandID = extractCommandID(commandFile);
			if (!executedCommandIds.contains(commandID)) {
				CommandInfo cInfo = (CommandInfo) readRemoteInstruction(getCommandFileName(commandID));
				if (cInfo == null) {
					continue;
				}
				sendAck(cInfo);
				pending.add(cInfo);
			}
		}
		return pending.toArray(new CommandInfo[pending.size()]);
	}

	protected void sendAck(CommandInfo commandInfo) {
		Acknowledge acknowledge = new Acknowledge(commandInfo.getCommandId());
		String xml = xstream.toXML(acknowledge);
		try {
			saveFile(getAckFileName(commandInfo.getCommandId()), xml);
		} catch (IOException e) {
			logger.error("", e);
		}
	}

	protected Object readRemoteInstruction(String fileName) {
		try {
			String content = readFile(fileName);
			Object object = xstream.fromXML(content);
			// not very pretty but quite efficient
			if (object instanceof Command) {
				Command command = (Command) object;
				CommandInfo commandInfo = new CommandInfo(command);
				commandInfo.setTimestamp(getTimeStamp(fileName));
				object = commandInfo;
			}
			return object;
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		}
	}

	@Override
	public boolean waitForAcknowledge(String commandId, long timeoutSec) {
		return waitForFile(getAckFileName(commandId), timeoutSec * 1000);
	}

	@Override
	public boolean waitForExecution(String commandId) {
		return waitForFile(getExecFileName(commandId), Long.MAX_VALUE);
	}

	protected String getAckFileName(String commandId) {
		return "ack_" + commandId + ".xml";
	}

	protected String getExecFileName(String commandId) {
		return "exec_" + commandId + ".xml";
	}

	protected String getCommandFileName(String commandId) {
		return "command_" + commandId + ".xml";
	}

	protected String extractCommandID(String fileName) {
		String[] splits = StringUtils.split(fileName, "_.");
		return splits[1] + "_" + splits[2];
	}

	protected boolean waitForFile(String fileName, long timeout) {
		long t1 = System.currentTimeMillis();
		while (System.currentTimeMillis() - t1 < timeout) {
			try {
				if (fileExists(fileName)) {
					return true;
				}
				Thread.sleep(5000);
			} catch (IOException e) {
				e.printStackTrace();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		return false;
	}

	@Override
	public ExecutionSummary readExecutionSummary(String commandId) {
		ExecutionSummary executionSummary = (ExecutionSummary) readRemoteInstruction(getExecFileName(commandId));
		return executionSummary;
	}

	@Override
	public void sendExecutionSummary(ExecutionSummary executionSummary) {
		String xml = xstream.toXML(executionSummary);
		try {
			saveFile(getExecFileName(executionSummary.getCommandId()), xml);
		} catch (IOException e) {
			logger.error("", e);
		}
	}

	protected void saveFile(String fileName, String content) throws IOException {
		String s = content;
		if (encryptCommands()) {
			s = Crypter.encrypt(s);
		}
		saveFileImpl(fileName, s);
	}

	protected String readFile(String fileName) throws IOException {
		String s = readFileContentsImpl(fileName);
		
		if (encryptCommands()) {
			s = Crypter.decrypt(s);
			if (s == null) {
				logger
						.warn(
								"File {} does not match current configuration and therefore deleted ...",
								fileName);
				deleteFile(fileName);
				return null;
			}
		}
		return s;
	}

	protected abstract void saveFileImpl(String fileName, String content) throws IOException;

	protected abstract String readFileContentsImpl(String fileName) throws IOException;

	protected abstract void deleteFile(String fileName) throws IOException;

	protected abstract Set<String> getFilesFromWorkDir(String fileNamePrefix) throws IOException;

	protected abstract Date getTimeStamp(String fileName) throws IOException;

	protected abstract boolean fileExists(String fileName) throws IOException;
}